# -*- coding: utf-8 -*-
# @Author  : CaoHan
# @Time    : 2023/12/1 16:10
import math
import random

import numpy as np

from ds.uav import BatteryJ, MEM, FLOPS, P_max, P_mid
from ds.enumClass import ContainerState
from ds.container import AppMem, AppFlops


def local(req, uavId, time, uavList, containerList):
    return False, -1, -1


def randomOffload(req, uavId, time, uavList, containerList):
    toUavId = random.randint(0, len(uavList) - 1)
    if toUavId == uavId:
        return False, -1, -1
    else:
        spareConIdNum = len(uavList[toUavId].spareConIdList)
        if spareConIdNum > 0:
            toSpareConId = random.randint(0, spareConIdNum - 1)
            return True, toUavId, uavList[toUavId].spareConIdList[toSpareConId]
        else:
            return True, toUavId, -1


def faasHouse(req, uavId, time, uavList, containerList, lastOffloadUavId):
    scores = []
    for i in range(len(uavList)):
        s = 0
        fromUav = uavList[uavId]
        toUav = uavList[i]
        # plugin_energy
        plugin_energy = (toUav.remainEnergy / fromUav.remainEnergy - 1) * 100 * getXt(fromUav) * getXt(toUav)
        if plugin_energy > 0:
            s += plugin_energy
        # plugin_locality
        if i == uavId:
            s += 200 * getXt(fromUav)
        # plugin_stickiness
        lastUavId = lastOffloadUavId[(req.fromSensorId, req.reqType.value)]
        if lastUavId == i and i != uavId:
            s += 40 * getXt(fromUav) * getXt(toUav)
        # plugin_uti：为了更好符合本文场景，自己加的plugin，根据剩余资源利用率
        plugin_uti = (toUav.remainFLOPS / fromUav.remainFLOPS - 1) * 100 * getXt(fromUav) * getXt(toUav)
        if plugin_uti > 0:
            s += plugin_uti
        scores.append(s)
    maxScore = -1
    bestUavId = -1
    for i in range(len(uavList)):
        if scores[i] > maxScore:
            maxScore = scores[i]
            bestUavId = i
    if bestUavId == uavId:
        return False, -1, -1
    else:
        if len(uavList[bestUavId].spareConIdList) > 0:
            return True, bestUavId, uavList[bestUavId].spareConIdList[0]
        else:
            return True, bestUavId, -1


def getXt(uav):
    if uav.remainEnergy / BatteryJ <= 0.1:
        return 0
    else:
        return 1


def offloadAll(req, uavId, time, uavList, containerList):
    fromUav = uavList[uavId]
    if fromUav.remainFLOPS >= AppFlops or len(fromUav.spareConIdList) > 0:
        return False, -1, -1
    # offload: 传给资源利用率最小的候选者
    candUavs = judgeBeOffloaded(uavList, uavId, 0, AppFlops / FLOPS)
    if len(candUavs) == 0:
        return False, -1, -1
    winnerId = -1
    lowestUti = 1
    for uav in candUavs:
        uti = 1 - uav.remainFLOPS / FLOPS
        if uti < lowestUti:
            lowestUti = uti
            winnerId = uav.id
    if len(uavList[winnerId].spareConIdList) > 0:
        return True, winnerId, uavList[winnerId].spareConIdList[0]
    else:
        return True, winnerId, -1


def ears(req, uavId, time, uavList, containerList, lastOffloadUavId):
    selfUavRemainERatio = uavList[uavId].remainEnergy / BatteryJ
    if selfUavRemainERatio > 0.75:
        return False, -1, -1
    cands = []
    for uav in uavList:
        if uav.id == uavId:
            continue
        toUavRemainERatio = uav.remainEnergy / BatteryJ
        if (toUavRemainERatio > 0.75) or (selfUavRemainERatio <= 0.25 < toUavRemainERatio <= 0.75):
            cands.append(uav)
    if len(cands) == 0:  # local
        return False, -1, -1
    else:
        candsSort = sorted(cands, key=lambda x: x.remainEnergy, reverse=True)
        # sticky offloading
        lastUavId = lastOffloadUavId[(req.fromSensorId, req.reqType.value)]
        lastUav = uavList[lastUavId]
        if (lastUav in candsSort) and (lastUav.remainFLOPS > AppFlops) and (lastUav.remainEnergy / BatteryJ > 0.25):
            if lastUav.remainEnergy / candsSort[0].remainEnergy >= 0.8:
                if len(lastUav.spareConIdList) > 0:
                    return True, lastUav.id, lastUav.spareConIdList[0]
                else:
                    return True, lastUav.id, -1
        for i in range(len(candsSort)):  # 从前往后遍历
            toUav = candsSort[i]
            if toUav.remainMem < AppMem and toUav.remainFLOPS < AppFlops:
                continue
            if 0.1 < selfUavRemainERatio <= 0.75:
                if len(toUav.spareConIdList) > 0:
                    return True, toUav.id, toUav.spareConIdList[0]
                else:
                    return True, toUav.id, -1
            if selfUavRemainERatio <= 0.1:
                if 0.75 < toUav.remainEnergy / BatteryJ:
                    if len(toUav.spareConIdList) > 0:
                        return True, toUav.id, toUav.spareConIdList[0]
                    else:
                        return True, toUav.id, -1
        return False, -1, -1


def score(req, uavId, time, uavList, containerList):
    nodes = []
    for uav in uavList:
        if uav.remainEnergy / BatteryJ > 0.25 and uav.remainMem > AppMem and uav.remainFLOPS > AppFlops:
            nodes.append(uav)
    if len(nodes) == 0:  # local
        return False, -1, -1
    else:
        # trade off module
        maxScore = -1
        bestId = -1
        fromUav = uavList[uavId]
        tc, E = fromUav.transTwithoutE(req.size, 50)  # 假设sensor和uav距离50m
        for toUav in nodes:
            dis_i = math.sqrt((fromUav.x - toUav.x) ** 2 + (fromUav.y - toUav.y) ** 2)
            if dis_i > 0:
                ti, E = fromUav.transTwithoutE(req.size, dis_i)
            else:
                ti = 0
            sc = (toUav.remainMem / MEM + toUav.remainFLOPS / FLOPS) / (1 + ti / tc)
            if sc > maxScore:
                maxScore = sc
                bestId = toUav.id
        if bestId == uavId:
            return False, -1, -1
        else:
            for cid in uavList[bestId].containerIdList:
                container = containerList[cid]
                if container.state == ContainerState.SPARE:
                    return True, bestId, cid
            return True, bestId, -1


def ecsoa(req, uavId, time, uavList, containerList, thre_1, thre_2):
    candUavs = judgeBeOffloaded(uavList, uavId, 0.05, 0.03)
    selfUav = uavList[uavId]
    if selfUav.remainEnergy <= thre_1 * P_max:
        """
                即将关机时，拒绝一切请求
        """
        return False, -200, -1
    elif selfUav.remainEnergy <= thre_2 * P_max:
        """
                电量不充足时，处于恢复阶段，只考虑自己的电量
        """
        if len(candUavs) == 0:
            return False, -1, -1
        # 卸载的能耗 vs 本地计算能耗(计算+[冷启动])
        selfE = (AppFlops / FLOPS) * (P_max - P_mid) * (req.flop / AppFlops)
        if selfUav.remainFLOPS / FLOPS < 0.03 and len(selfUav.spareConIdList) == 0:  # 本地资源已满
            selfE *= 1
        else:
            if len(selfUav.spareConIdList) == 0:
                selfE += selfUav.getColdStartE()
        minE = 100000
        bestUav = candUavs[0]
        for cand in candUavs:
            t, e = selfUav.transTwithoutE(req.size, math.sqrt((selfUav.x - cand.x) ** 2 + (selfUav.y - cand.y) ** 2))
            if e < minE:
                minE = e
                bestUav = cand
        if selfE <= minE:
            return False, -1, -1
        else:
            return selectContainer(bestUav, containerList)
    else:
        """
                电量充足时，考虑整体的电量
        """
        if len(candUavs) > 0 and len(selfUav.spareConIdList) == 0:
            # 本地冷启动能耗 vs 冷启动+传输能耗
            if selfUav.remainFLOPS / FLOPS < 0.03:  # 本地资源已满
                selfE = selfUav.getColdStartE() * 1.5  # 传输能耗不能高于0.5个冷启动能耗
            else:
                selfE = selfUav.getColdStartE()
            minE = 100000
            bestUav = candUavs[0]
            for cand in candUavs:
                t, e = selfUav.transTwithoutE(req.size,
                                              math.sqrt((selfUav.x - cand.x) ** 2 + (selfUav.y - cand.y) ** 2))
                if len(cand.spareConIdList) == 0:
                    e += cand.getColdStartE()
                if e < minE:
                    minE = e
                    bestUav = cand
            if selfE <= minE:
                return False, -1, -1
            else:
                return selectContainer(bestUav, containerList)
        else:
            return False, -1, -1


def selectContainer(bestUav, containerList):
    if len(bestUav.spareConIdList) == 0:
        return True, bestUav.id, -1
    else:
        bestId = 0
        latestTime = -1
        for id in bestUav.spareConIdList:
            c = containerList[id]
            if latestTime < c.runTime[-1].end:
                latestTime = c.runTime[-1].end
                bestId = id
        return True, bestUav.id, bestId


def judgeBeOffloaded(uavList, uavId, battery_thresh, resource_thresh):
    candUavs = []  # 可以被卸载的Uav(不包括自己)
    for uav in uavList:
        if uav.remainEnergy / BatteryJ <= battery_thresh or (
                (uav.remainFLOPS / FLOPS < resource_thresh or uav.remainMem / MEM < resource_thresh) and len(
            uav.spareConIdList) == 0):
            if uav.beOffloaded:
                uav.beOffloaded = False
        else:
            if not uav.beOffloaded:
                uav.beOffloaded = True
        if uav.beOffloaded and uav.id != uavId:
            candUavs.append(uav)
    return candUavs
